package com.fastloan.mgt.application.base.service.impl;

import com.alibaba.fastjson.JSON;
import com.fastloan.common.BasicServiceImpl;
import com.fastloan.common.model.CheckResult;
import com.fastloan.mgt.application.approve.model.vo.ApplicationApproveHistoryVO;
import com.fastloan.mgt.application.approve.service.ApplicationApproveHistoryService;
import com.fastloan.mgt.application.base.dao.ApplicationMapper;
import com.fastloan.mgt.application.base.enums.ApplicationStatusEnum;
import com.fastloan.mgt.application.base.model.*;
import com.fastloan.mgt.application.base.service.ApplicationIPCService;
import com.fastloan.mgt.application.base.service.ApplicationService;
import com.fastloan.mgt.application.base.util.ApplicationPDF;
import com.fastloan.mgt.application.base.util.CodeUtil;
import com.fastloan.mgt.application.contract.dao.ApplicationLoanResultMapper;
import com.fastloan.mgt.application.contract.model.ApplicationContract;
import com.fastloan.mgt.application.contract.service.ApplicationContractService;
import com.fastloan.mgt.application.contract.service.impl.loanmode.LoanMode;
import com.fastloan.mgt.application.phoneapprove.model.ApplicationPhoneApprove;
import com.fastloan.mgt.application.phoneapprove.service.ApplicationPhoneApproveService;
import com.fastloan.mgt.application.repay.model.vo.AppCurrRepayExt;
import com.fastloan.mgt.customer.enums.CustomerEnums;
import com.fastloan.mgt.customer.model.ZyCustomer;
import com.fastloan.mgt.customer.model.ZyCustomerCard;
import com.fastloan.mgt.customer.service.ZyCustomerCardService;
import com.fastloan.mgt.customer.service.ZyCustomerService;
import com.fastloan.mgt.processcontrol.base.annotation.FunctionHandlerClass;
import com.fastloan.mgt.processcontrol.base.annotation.FunctionHandlerMethod;
import com.fastloan.mgt.processcontrol.base.model.ProcessSchedule;
import com.fastloan.mgt.processcontrol.base.service.ProcessDispatcher;
import com.fastloan.mgt.processcontrol.base.service.ProcessFunctionService;
import com.fastloan.mgt.processcontrol.base.util.BeanFactoryUtil;
import com.fastloan.mgt.product.basics.model.Product;
import com.fastloan.mgt.product.basics.model.ProductConditions;
import com.fastloan.mgt.product.basics.service.ProductService;
import com.fastloan.mgt.product.flow.enums.ProductFunctionGroup;
import com.fastloan.mgt.product.flow.model.ProductFlow;
import com.fastloan.mgt.product.flow.service.ProductFlowService;
import com.fastloan.safe.core.param.RoleTypeEnum;
import com.fastloan.safe.core.util.ManagerKit;
import com.fastloan.safe.mgt.auth.model.CmuRole;
import com.fastloan.safe.mgt.auth.service.CmuRoleService;
import com.fastloan.safe.mgt.org.model.CmuOrgUser;
import com.fastloan.safe.mgt.org.service.CmuOrgService;
import com.fastloan.safe.mgt.org.service.CmuOrgUserService;
import com.fastloan.safe.mgt.user.model.CmuUser;
import com.fastloan.safe.mgt.user.service.CmuUserService;
import com.fastloan.utils.DozerUtil;
import com.kawins.result.ResultEnum;
import com.kawins.result.ResultTo;
import com.kawins.supers.table.manager.DataTable;
import com.kawins.util.IDCardUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.io.OutputStream;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import static com.fastloan.mgt.product.enums.ProductEnum.PUBLISHED;
import static com.fastloan.mgt.product.enums.ProductType.ADVANCE;

/**
 * @author yanwe
 * createTime 2017-11-2017/11/8 14:32
 */
@Service
@FunctionHandlerClass(processName = "补充调查", bmpIds = {ProductFunctionGroup.GROUP_ONE})
public class ApplicationServiceImpl extends BasicServiceImpl<Application> implements ApplicationService {

    @Autowired
    private ApplicationMapper applicationMapper;

    @Autowired
    private ApplicationIPCService applicationIPCService;

    @Autowired
    private ApplicationContractService applicationContractService;

    @Autowired
    private ApplicationPhoneApproveService applicationPhoneApproveService;

    @Autowired
    private ApplicationApproveHistoryService applicationApproveHistoryService;

    @Autowired
    private ProcessFunctionService processFunctionService;

    @Autowired
    private ProductFlowService productFlowService;

    @Autowired
    private ZyCustomerService customerService;

    @Autowired
    private ProductService productService;

    @Autowired
    private CmuUserService userService;

    @Autowired
    private CmuRoleService roleService;

    @Autowired
    private CmuOrgService orgService;

    @Autowired
    private CmuOrgUserService orgUserService;

    @Autowired
    private ZyCustomerCardService customerCardService;

    @Autowired
    private ApplicationLoanResultMapper applicationLoanResultMapper;

    @Override
    public ApplicationVo selectApplicationVo(String applicationId) {
        Map<String, Object> map = new HashMap<>();
        map.put("applicationId", applicationId);
        List<ApplicationVo> applicationVos = selectApplicationVoList(map);
        return applicationVos.isEmpty() ? null : applicationVos.get(0);
    }

    @Override
    @DataTable(name = "selectApplicationVoList")
    public List<ApplicationVo> selectApplicationVoList(Map<String, Object> map) {
        map.computeIfAbsent("orgId", k -> ManagerKit.getOrgId());
        return applicationMapper.selectApplicationVo(map);
    }

    @Override
    @Transactional
    public ResultTo submitApplication(ApplicationVo applicationVo) {
        Product product = productService.selectByPrimaryKey(applicationVo.getProductId());
        //预授信类型不检查
        if (!product.getProductType().equals(ADVANCE.getType())){
            //检查能否申请
            CheckResult checkResult = checkApplication(applicationVo, product);
            if (!checkResult.isCheckResult()) {
                log.debug("进件申请失败！:" + checkResult.getCheckMsg());
                return new ResultTo(ResultEnum.OPERATION_FAILED, checkResult.getCheckMsg());
            }
        }
        //绑定客户经理
        bindManager(applicationVo);
        //绑定机构
        bindOrg(applicationVo, product);
        //更新客户信息
        updateCustomerInfo(applicationVo);
        //生成进件
        Application application = createApplication(applicationVo);
        //生成IPC
        createIPC(applicationVo, application);
        //创建并启动流程
        String processId = startProcess(application);
        ProcessSchedule currentProcess = ProcessDispatcher.getCurrentProcess(processId);
        return new ResultTo().setData("nextProcess", currentProcess)
                .setData("applicationId", application.getApplicationId());
    }

    @Override
    @FunctionHandlerMethod
    public void startSurvey(String processId) {
        String applicationId = ProcessDispatcher.getProcessSession(processId)
                .getAttribute("applicationId", String.class);
        modifyApplicationStatus(applicationId, ApplicationStatusEnum.WAIT_SURVEY);
    }

    @Override
    public synchronized void endSurvey(String applicationId) {
        Application application = selectByPrimaryKey(applicationId);
        ProcessSchedule currentProcess = ProcessDispatcher.getCurrentProcess(application.getProcessId());
        if (currentProcess.getProcessClass().equals(this.getClass().getSimpleName())) {
            ProcessDispatcher.executeEnd(application.getProcessId(), true);
        } else {//不是待调查则重新执行流程
            ProcessDispatcher.redoCurrentFunction(application.getProcessId());
        }
    }

    @Override
    public void modifyApplicationStatus(String applicationId, ApplicationStatusEnum applicationStatusEnum) {
        Application application = new Application();
        application.setApplicationId(applicationId);
        application.setApplicationStatus(applicationStatusEnum.getType());
        updateByPrimaryKeySelective(application);
    }

    @Override
    @DataTable(name = "queryMineApp")
    public List<AppCurrRepayExt> queryMineApp(Map<String, Object> map) {
        List<AppCurrRepayExt> appCurrRepayExtList = applicationMapper.queryMineApp(map);
        Map<String, List<AppCurrRepayExt>> loanApplicationList = appCurrRepayExtList.stream()
                .filter(appCurrRepayExt -> ApplicationStatusEnum.WAIT_REPAYMENT
                        .equals(ApplicationStatusEnum.getByType(appCurrRepayExt.getApplicationStatus())))
                .collect(Collectors.groupingBy(appCurrRepayExt ->
                        productService.selectByPrimaryKey(appCurrRepayExt.getProductId()).getFundingId()));
        Map<String, LoanMode> loanModeMap = BeanFactoryUtil.getBeansOfType(LoanMode.class);
        loanApplicationList.forEach((fundingId, loanApplications) ->
                loanModeMap.values().stream()
                        .filter(loanMode -> loanMode.loanMode().getType().equals(fundingId))
                        .forEach(loanMode -> loanApplications.forEach(loanMode::updateApplicationList)));
        return appCurrRepayExtList;
    }

    @Override
    public void createApplicationPDF(String applicationId, OutputStream outputStream) {
        ApplicationVo applicationVo = selectApplicationVo(applicationId);
        CompletableFuture<Product> productCompletableFuture =
                CompletableFuture.supplyAsync(() -> productService.selectByPrimaryKey(applicationVo.getProductId()));
        CompletableFuture<ApplicationContract> applicationContractCompletableFuture =
                CompletableFuture.supplyAsync(() -> applicationContractService.getContractInfo(applicationId));
        CompletableFuture<ApplicationPhoneApprove> applicationPhoneApproveCompletableFuture =
                CompletableFuture.supplyAsync(() -> applicationPhoneApproveService.getApproveInfo(applicationId));
        CompletableFuture<List<ApplicationApproveHistoryVO>> applicationApproveHistoryFuture =
                CompletableFuture.supplyAsync(() -> applicationApproveHistoryService.queryApproveHistory(applicationId));
        CompletableFuture<List<ApplicationIPC>> customerIPCFuture =
                CompletableFuture.supplyAsync(() -> applicationIPCService.selectApplicationIPC(applicationId, 0));
        CompletableFuture<List<ApplicationIPC>> customerManagerIPCFuture =
                CompletableFuture.supplyAsync(() -> applicationIPCService.selectApplicationIPC(applicationId, 1));
        ApplicationPdfInfo applicationPdfInfo = new ApplicationPdfInfo();
        applicationPdfInfo.setApplication(applicationVo);
        applicationPdfInfo.setProduct(productCompletableFuture.join());
        applicationPdfInfo.setApplicationContract(applicationContractCompletableFuture.join());
        applicationPdfInfo.setApplicationPhoneApprove(applicationPhoneApproveCompletableFuture.join());
        applicationPdfInfo.setExpertApproveList(applicationApproveHistoryFuture.join());
        applicationPdfInfo.setCustomerIPC(customerIPCFuture.join());
        applicationPdfInfo.setCustomerManagerIPC(customerManagerIPCFuture.join());
        try {
            ApplicationPDF.createPdf(applicationPdfInfo, outputStream);
        } catch (IOException e) {
            e.printStackTrace();
            log.error("进件导出错误！进件ID:" + applicationId);
        }
    }

    /**
     * 验证是否可以申请产品
     *
     * @param applicationVo 进件申请信息
     * @return true 可以申请 false 不能申请
     */
    private CheckResult checkApplication(ApplicationVo applicationVo, Product product) {
        //验证产品规则
        CheckResult checkResult = verifyProductRule(applicationVo);
        if (!checkResult.isCheckResult()) {
            return checkResult;
        }
        Application criteria = new Application();
        criteria.setCustomerId(applicationVo.getCustomerId());
        criteria.setProductId(applicationVo.getProductId());
        int count = selectCount(criteria);
        if (count != 0 && Objects.equals(product.getRepetitionApply(), PUBLISHED.getType())) {
            return CheckResult.fail("不能重复申请该产品!");
        }
        List<String> errorMsgList = new ArrayList<>();
        if (applicationVo.getApplyAmount().compareTo(product.getSpendingLimit()) > 0 ||
                applicationVo.getApplyAmount().compareTo(product.getSpendingFloor()) < 0) {
            errorMsgList.add("申请额度:" + applicationVo.getApplyAmount()
                    + "不符合产品额度规定!规定区间:" + product.getSpendingFloor() + "~" + product.getSpendingLimit() + "!");
        }
        if (applicationVo.getApplyPeriod() > product.getTimeLimit() ||
                applicationVo.getApplyPeriod() < product.getTimeFloor()) {
            errorMsgList.add("申请期限:" + applicationVo.getApplyAmount()
                    + "不符合产品期限规定!规定区间:" + product.getTimeFloor() + "~" + product.getTimeLimit() + "!");
        }
        int customerAge = IDCardUtil.getAgeByIDcard(applicationVo.getCardNum());
        if (customerAge > product.getAccessLimit() || customerAge < product.getAccessFloor()) {
            errorMsgList.add("申请人年龄:" + customerAge
                    + ",不符合产品年龄准入规定!规定区间:" + product.getAccessFloor() + "~" + product.getAccessLimit() + "!");
        }
        String errorMsg = errorMsgList.stream().collect(Collectors.joining(","));
        return StringUtils.isBlank(errorMsg) ? CheckResult.success() : CheckResult.fail(errorMsg);
    }

    @Override
    public CheckResult verifyProductRule(ApplicationVo applicationVo) {
        Product product = productService.selectByPrimaryKey(applicationVo.getProductId());
        if (product == null) {
            return CheckResult.fail("产品不存在:" + applicationVo.getProductId());
        }
        //获取产品对应验证规则
        if (product.getConditions() == null) {
            return CheckResult.success();
        }
        List<ProductConditions> productConditions = JSON.parseArray(product.getConditions(), ProductConditions.class);
        List<ZyCustomerCard> customerCard = customerCardService.getCustomerCard(applicationVo.getCustomerId());
        if (productConditions != null && !productConditions.isEmpty()) {
            for (ProductConditions productCondition : productConditions) {
                switch (productCondition.getType()) {
                    //人脸识别
                    case 0:
                        ZyCustomer customer = customerService.selectByPrimaryKey(applicationVo.getCustomerId());
                        if (customer == null){
                            return CheckResult.fail("该客户不存在:"+applicationVo.getCustomerId());
                        }
                        if (!Objects.equals(customer.getVerify(), CustomerEnums.VerifyEnum.IS_VERIFY.getCode())) {
                            return CheckResult.fail("该客户未通过验证，无法申请进件！", productCondition.getType());
                        }
                        break;
                    //银行卡
                    case 1:
                        if (customerCard.isEmpty()) {
                            return CheckResult.fail("该客户未绑定银行卡，无法申请进件！", productCondition.getType());
                        }
                        break;
                }
            }
        }
        return CheckResult.success();
    }

    private String startProcess(Application application) {
        String processId = processFunctionService.createProcess(ProductFunctionGroup.GROUP_ONE, procId -> {
            ProductFlow criteria = new ProductFlow();
            criteria.setStatus(0);
            criteria.setProductId(application.getProductId());
            return productFlowService.select(criteria).stream().map(productFlow ->
                    new ProcessFlow(procId, productFlow.getFlowId())).collect(Collectors.toList());
        });
        application.setProcessId(processId);//绑定进件和流程
        updateByPrimaryKeySelective(application);
        ProcessDispatcher.getProcessSession(processId).setAttribute("applicationId", application.getApplicationId());
         ProcessDispatcher.startProcess(processId);
        return processId;
    }

    private void createIPC(ApplicationVo applicationVo, Application application) {
        List<ApplicationValue> ipcValueList = JSON.parseArray(applicationVo.getIpcValue(), ApplicationValue.class);
        List<ApplicationIPC> customerManagerIPCS = applicationIPCService
                .selectProductIPC(applicationVo.getProductId(), 1);
        List<ApplicationValue> noTreeCustomerManagerIPC = new ArrayList<>();
        for (ApplicationIPC customerManagerIPC : customerManagerIPCS) {
            noTreeCustomerManagerIPC.add(convert2ApplicationValue(customerManagerIPC));
            for (ApplicationIPC childIPC : customerManagerIPC.getChildList()) {
                noTreeCustomerManagerIPC.add(convert2ApplicationValue(childIPC));
            }
        }
        noTreeCustomerManagerIPC.removeIf(customerManagerIPC -> ipcValueList.stream()
                .anyMatch(ipcValue -> customerManagerIPC.getProductTempId().equals(ipcValue.getProductTempId())));
        ipcValueList.addAll(noTreeCustomerManagerIPC);
        ipcValueList.forEach(applicationValue -> applicationValue.setApplicationId(application.getApplicationId()));
        applicationIPCService.addIPC(ipcValueList);
    }

    private Application createApplication(ApplicationVo applicationVo) {
        Application application = DozerUtil.map(applicationVo, Application.class);
        application.setCreateTime(new Date());
        application.setApplyTime(new Date());
        application.setApplicationCode(CodeUtil.getApplicationCode());
        insertSelective(application);
        return application;
    }

    private void bindOrg(ApplicationVo applicationVo, Product product) {
        applicationVo.setOrgId(product.getOrgId());
    }

    //TODO:此处需要判断绑定的客户经理是否和客户申请的产品在同一个机构,如果不在需要随机分配一个产品所在机构的客户经理
    private void bindManager(ApplicationVo applicationVo) {
        ZyCustomer customer = customerService.selectByPrimaryKey(applicationVo.getCustomerId());
        String managerId = customer.getCustomerManagerId();
        if (managerId != null) {
            CmuOrgUser orgUser = orgUserService.selectOne(new CmuOrgUser().setUserId(managerId));
            Product product = productService.selectByPrimaryKey(applicationVo.getProductId());
            if (orgUser.getOrgId().equals(product.getOrgId())) {
                applicationVo.setCustomerManagerId(managerId);
                return;
            }
        }
        //TODO:此处约束一个roleType只能有一个角色,不能新建
        CmuRole cmuRole = new CmuRole();
        cmuRole.setIndexType(RoleTypeEnum.MANAGER.getCode());
        cmuRole.setTopOrgId(orgService.selectTopOrgBySon(applicationVo.getOrgId(), false, true).get(0).getId());
        CmuRole role = roleService.selectOne(cmuRole);
        if (role != null) {
            Product product = productService.selectByPrimaryKey(applicationVo.getProductId());
            List<CmuUser> cmuUsers = userService.selectByRoleId(role.getId(), product.getOrgId());
            if (cmuUsers.size() == 0) {
                log.error("该进件所在机构没有客户经理");
            }
            managerId = cmuUsers.stream().findAny().map(CmuUser::getId)
                    .orElse(null);
        }
        //TODO:此处需要给分配到的客户经理发送通知
        CmuUser cmuUser = userService.selectByPrimaryKey(managerId);
        log.info("最新进件,随机分配的客户经理账号:{}", Optional.ofNullable(cmuUser)
                .map(CmuUser::getUsername).orElse("没有客户经理"));
        applicationVo.setCustomerManagerId(managerId);
    }

    private void updateCustomerInfo(ApplicationVo applicationVo) {
        ZyCustomer customer = new ZyCustomer();
        customer.setId(applicationVo.getCustomerId());
        customer.setCname(applicationVo.getCname());
        customer.setCardNum(applicationVo.getCardNum());
        customer.setCustomerManagerId(applicationVo.getCustomerManagerId());
        customer.setOrgId(applicationVo.getOrgId());
        customer.setStatus(CustomerEnums.CustomerStatus.STOCK.getCode());
        customer.setGender(IDCardUtil.getSexByIDCard(applicationVo.getCardNum())
                .equals(IDCardUtil.SexEnum.MAN) ? CustomerEnums.Gender.MALE.getCode()
                : CustomerEnums.Gender.FEMALE.getCode());
        customerService.updateByPrimaryKeySelective(customer);
    }

    private ApplicationValue convert2ApplicationValue(ApplicationIPC applicationIPC) {
        ApplicationValue applicationValue = new ApplicationValue();
        applicationValue.setProductTempId(applicationIPC.getProductTempId());
        return applicationValue;
    }
}
